﻿/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/

using System;
using System.Collections;
using System.Xml;
using System.Xml.Schema;
using System.Qizx.Api;
using System.IO;
using System.Qizx.XQuery.Dt;
using DataModelException = System.Qizx.Api.DataModelException;
using EvaluationException = System.Qizx.Api.EvaluationException;
using Node = System.Qizx.Api.Node;
using QName = System.Qizx.Api.QName;
using QizxException = System.Qizx.Api.QizxException;
using XMLPullStream = System.Qizx.Api.XMLPullStream;
using PushStreamToDOM = System.Qizx.Api.Util.PushStreamToDOM;
using XQDateTimeBase = System.Qizx.Api.Util.XQDateTimeBase;
using DateTimeException = System.Qizx.Api.Util.XQDateTimeException;
using Duration = System.Qizx.Api.Util.XQDuration;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using Comparison = System.Qizx.Util.Basic.Comparison;
using FileUtil = System.Qizx.Util.Basic.FileUtil;
using ComparisonContext = System.Qizx.XQuery.ComparisonContext;
using XQItem = System.Qizx.XQuery.XQItem;
using XQValue = System.Qizx.XQuery.XQValue;
using StringValue = System.Qizx.XQuery.Dt.StringValue;
using UntypedAtomicType = System.Qizx.XQuery.Dt.UntypedAtomicType;
using URI = System.Uri;

namespace System.Qizx.Xdm {
    [Serializable]
    public abstract class XdmNode : BasicNode, System.Collections.IEnumerable {
        public static XdmNode[] EmptyArray = new XdmNode[0];

        private CoreDataModel _coreDataModel;
        internal CoreDataModel DataModel { get { return _coreDataModel; } }

        public XdmNode(CoreDataModel model) {
            _coreDataModel = model;
        }

        /// <summary>
        /// subclass can override, in order to give another XmlSchemaSet
        /// </summary>
        public override XmlSchemaSet Schemas {
            get {
                XdmDocument ownerDoc = this.OwnerDocument;
                return ownerDoc != null ? ownerDoc.Schemas : new XmlSchemaSet();
            }
        }

        /// <summary>
        /// Gets the base URI.
        /// </summary>
        /// <value>The base URI.</value>
        public override string BaseURI {
            get {
                IQName base_Renamed = IQName.GetQName(NamespaceContext.XML, "base");
                XdmNodeType kind = NodeType;
                if (kind != System.Qizx.Api.XdmNodeType.Document && kind != System.Qizx.Api.XdmNodeType.Element)
                    return null;
                return GetBaseUri(this, base_Renamed);
            }

        }

        public virtual void RemoveAll() {
        }

        virtual public bool Remote {
            get {
                return false;
            }

        }
        public override abstract XdmNodeType NodeType { get; }
        public override abstract string NodeKind { get; }
        public override string DocumentURI {
            get {
                XdmDocument doc = this.OwnerDocument;
                if (doc != null)
                    return doc.BaseURI;
                return null;
            }

        }
        public override QName NodeName {
            get {
                return null;
            }
        }

        public override string ValueAsString {
            get {
                try {
                    return StringValue;
                } catch (DataModelException e) {
                    throw BasicNode.WrapDMException(e);
                }
            }

        }

        virtual internal XdmNode Top {
            get {
                XdmNode n = this;
                for (; n._parent != null; )
                    n = n._parent;
                return n;
            }

        }
        virtual public int DefinedNSCount {
            get {
                return 0;
            }

        }
        virtual public bool Atom {
            get {
                return false;
            }

        }
        public override char[] CharValue {
            // ---- typing extensions:


            get {
                throw new System.SystemException("getChars of a non-atom");
            }

        }
        virtual public System.Object Value {
            get {
                return null; // not implemented
            }

        }
        virtual public long IntegerValue {
            get {
                return -1; // not implemented
            }

        }
        public override System.Object Object {
            get {
                return new PushStreamToDOM().exportNode(this);
            }

        }
        public override Duration Duration {
            get {
                try {
                    return Duration.parseDuration(ValueAsString);
                } catch (DateTimeException e) {
                    throw new EvaluationException(e.Message);
                }
            }

        }

        public override int AttributeCount {
            get {
                return 0;
            }

        }

        internal XdmElement _parent;
        internal XdmNode _nextSibling;

        internal int order = -1; // TODO ? seems broken

        public override string ToString() {
            try {
                return "Core " + NodeKind + " " + (NodeName != null ? (" name=" + NodeName) : StringValue);
            } catch (DataModelException e) {
                return "[[" + e + "]]";
            }
        }

        private string GetBaseUri(XdmNode node, IQName baseName) {

            for (XdmNode n = node; n != null; n = n._parent) {
                XdmNode attr = (XdmNode)n.GetAttributeNode(baseName);
                if (attr != null) {
                    string frag = attr.StringValue;
                    // if absolute URI, that's it:
                    URI uri = FileUtil.uriConvert(frag);
                    if (uri == null || uri.IsAbsoluteUri)
                        return frag;
                    // otherwise resolve against baseURI of parent
                    return FileUtil.resolve(GetBaseUri(n._parent, baseName), frag);
                }
            }

            #region override BaseURI in document
            if (!string.IsNullOrEmpty(_coreDataModel._baseURI))
                return _coreDataModel._baseURI;
            XdmDocument document = node.OwnerDocument;
            if (document != null && node != document) {
                string uri = document.BaseURI;
                if (!string.IsNullOrEmpty(uri)) {
                    _coreDataModel._baseURI = uri;
                }
            }
            #endregion
            return _coreDataModel._baseURI;
        }

        public abstract override bool IsElement { get; }

        public abstract NodeSequenceBase GetChildren();

        public override Node ParentNode {
            get { return _parent; }
        }

        public new XdmNode FirstChild {
            get {
                return GetFirstChild() as XdmNode;
            }
        }

        public override BasicNode NextSibling {
            get {
                //HACK change _lastChild._nextSibling to the firstChild
                if (this._parent != null && this._parent._lastChild != this)
                    return _nextSibling;
                return null;
            }
        }

        public override int DocumentOrderCompareTo(Node xnode) {
            if (xnode == this)
                return 0;
            BasicNode node = (BasicNode)xnode;
            if (!(node is XdmNode))
                return Comparison.of(DocPosition(), node.DocPosition());

            XdmNode that = (XdmNode)node;
            XdmNode top = Top, thatTop = that.Top;
            if (top != thatTop)
                return Comparison.of(top.DocPosition(), thatTop.DocPosition());
            // if possible, use order stamps:
            if (order >= 0 && that.order >= 0)
                return Comparison.of(order, that.order);
            // inefficient, but not supposed to be used heavily:
            for (; that != null; that = (XdmNode)that.NextNode)
                if (that == this)
                    return 1;
            return -1;
        }

        public override bool Contains(Node node) {
            if (!(node is XdmNode))
                return false;

            for (XdmNode n = (XdmNode)node; n != null; n = n._parent)
                if (n == this)
                    return true;
            return false;
        }

        public override bool Equals(System.Object that) {
            return this == that;
        }

        // Redefined here just for pleasing code verifiers
        public override int GetHashCode() {
            return base.GetHashCode();
        }

        public override bool DeepEquals(XQItem item, ComparisonContext context) {
            if (!(item is Node))
                return false;
            try {
                return DeepEq((BasicNode)item.GetNode(), context.getCollator());
            } catch (DataModelException e) {
                return false; // should not happen
            }
        }

        public override bool DeepEquals(Node item, System.Globalization.CompareInfo collator) {
            return DeepEq((BasicNode)item, collator);
        }

        public virtual bool DeepEq(BasicNode that, System.Globalization.CompareInfo collator) {
            XdmNodeType kind = NodeType;
            if (kind != that.NodeType || this.NodeName != that.NodeName)
                return false;
            if (kind == System.Qizx.Api.XdmNodeType.Document)
                return ((XdmElement)this).ContentEq(that, collator);
            else if (kind == System.Qizx.Api.XdmNodeType.Element)
                return ((XdmElement)this).AttributesEq(that, collator) && ((XdmElement)this).ContentEq(that, collator);
            // compare by string value
            else
                return ValueEq(that, collator);
        }

        protected internal virtual bool ValueEq(Node that, System.Globalization.CompareInfo collator) {
            return System.Qizx.XQuery.Dt.StringValue.compare(this.StringValue, that.StringValue, collator) == Comparison.EQ;
        }

        public override int DocPosition() {
            return Top.GetHashCode();
        }

        // ---- extensions for XPath steps:

        public override NodeSequenceBase GetAncestors(NodeFilter nodeTest) {
            return new Ancestors(this, nodeTest);
        }

        public override NodeSequenceBase GetAncestorsOrSelf(NodeFilter nodeTest) {
            return new AncestorsOrSelf(this, nodeTest);
        }

        public override NodeSequenceBase GetParent(NodeFilter nodeTest) {
            return new Parent(this, nodeTest);
        }

        public override NodeSequenceBase GetChildren(NodeFilter nodeTest) {
            //            if(reuse instanceof Children) {
            //                return ((Children) reuse).restart(this, nodeTest);
            //            }
            return new Children(this, nodeTest);
        }

        public override NodeSequenceBase GetDescendants(NodeFilter nodeTest) {
            return new Descendants(this, nodeTest);
        }

        public override NodeSequenceBase GetDescendantsOrSelf(NodeFilter nodeTest) {
            return new DescendantsOrSelf(this, nodeTest);
        }

        public override NodeSequenceBase GetAttributes(NodeFilter nodeTest) {
            return new Attributes(this, nodeTest);
        }

        public override NodeSequenceBase GetFollowingSiblings(NodeFilter nodeTest) {
            return new FollowingSiblings(this, nodeTest);
        }

        public override NodeSequenceBase GetPrecedingSiblings(NodeFilter nodeTest) {
            return new PrecedingSiblings(this, nodeTest);
        }

        public override NodeSequenceBase GetFollowing(NodeFilter nodeTest) {
            return new Following(this, nodeTest);
        }

        public override NodeSequenceBase GetPreceding(NodeFilter nodeTest) {
            return new Preceding(this, nodeTest);
        }

        internal virtual void SetNextSibling(XdmNode sibling) {
            _nextSibling = sibling;
            if (sibling != null)
                sibling._parent = _parent;
        }

        public override BasicNode[] GetIdMatchingNodes(string id, bool idref) {
            return null; // wont be implemented: no validation!
        }

        // -------- implements dm.Node ----------------------------------------

        public virtual void AddText(string text) {
        }

        public override string GetPrefixOfNamespace(string nsuri) {
            return null;
        }

        public override string GetNamespaceOfPrefix(string prefix) {
            return null;
        }

        public override void AddInScopeNamespacesTo(NamespaceContext nsContext) {
        }

        public override int AddNamespacesTo(NamespaceContext nsContext) {
            return 0;
        }

        public override int AddUsedNamespacesTo(NamespaceContext nsContext) {
            return 0;
        }

        public virtual Node Document() {
            return OwnerDocument;
        }

        public virtual Node Attribute(IQName name) {
            return GetAttributeNode(name);
        }

        public override XMLPullStream ExportNode() {
            return new NodePullStream(this);
        }

        public override BasicNode GetBasicNode() {
            return this;
        }

        public override int CompareTo(XQItem that, ComparisonContext context, int flags) {
            try {
                IXdmSchemaInfo schemaInfo = this.SchemaInfo;
                if (schemaInfo != null && schemaInfo.DataType != null) {
                    switch (schemaInfo.DataType.TypeCode) {
                        case XmlTypeCode.Decimal:
                            return this.ValueAsDecimal.CompareTo(that.ValueAsDecimal);
                        case XmlTypeCode.Date:
                        case XmlTypeCode.DateTime:
                        case XmlTypeCode.Time:
                            return this.ValueAsDateTime.CompareTo(that.ValueAsDateTime);
                        case XmlTypeCode.Int:
                        case XmlTypeCode.Integer:
                        case XmlTypeCode.Long:
                        case XmlTypeCode.UnsignedLong:
                        case XmlTypeCode.Short:
                        case XmlTypeCode.UnsignedShort:
                        case XmlTypeCode.NonNegativeInteger:
                        case XmlTypeCode.NonPositiveInteger:
                        case XmlTypeCode.PositiveInteger:
                        case XmlTypeCode.Byte:
                        case XmlTypeCode.UnsignedByte:
                            return this.ValueAsInteger.CompareTo(that.ValueAsInteger);
                        case XmlTypeCode.Double:
                            return this.ValueAsDouble.CompareTo(that.ValueAsDouble);
                        case XmlTypeCode.Float:
                            return this.ValueAsFloat.CompareTo(that.ValueAsDouble);
                        case XmlTypeCode.GDay:
                        case XmlTypeCode.GMonth:
                        case XmlTypeCode.GMonthDay:
                        case XmlTypeCode.GYear:
                        case XmlTypeCode.GYearMonth:
                            return this.ValueAsMoment.CompareTo(that.ValueAsMoment);
                        case XmlTypeCode.DayTimeDuration:
                        case XmlTypeCode.YearMonthDuration:
                        case XmlTypeCode.Duration:
                            return this.ValueAsMoment.CompareTo(that.ValueAsMoment);
                        case XmlTypeCode.QName:
                            return this.ValueAsQName.CompareTo(that.ValueAsQName);
                        case XmlTypeCode.AnyAtomicType:
                            //TODO coshare compare;
                            if (schemaInfo.DataType.Variety == XmlSchemaDatatypeVariety.Union) {
                                string text = this.StringValue;
                                object value = schemaInfo.DataType.ParseValue(text, this.OwnerDocument.NameTable, null);
                                if (value != null) {
                                    if (value is DateTime) {
                                        if (text.Contains("T")) {
                                            return this.ValueAsDateTime.CompareTo(that.ValueAsDateTime);
                                        } else {
                                            return this.ValueAsDate.CompareTo(that.ValueAsDate);
                                        }
                                    }
                                }
                            }
                            break;
                        default:

                            break;
                    }
                }
            } catch {
                return System.Qizx.Util.Basic.Comparison.ERROR;
            }

            // TODO: optimise by traversal comparison (stream on the stringvalue)
            //return UntypedAtomicType.comparison(this, that, context, flags); //StackOverflow
            // both items are UNTYPED_ATOMIC/Node: string compare
            return System.Qizx.XQuery.Dt.StringValue.compare(this.ValueAsString, that.ValueAsString, (context != null) ? context.getCollator() : null);
        }

        // ------------------ node ---------------------------------------

        public override Node GetAttributeNode(System.Xml.XmlQualifiedName name) {
            return null;
        }

        public override Node[] Attributes {
            get { return null; }
        }

        #region Extension methods
        public virtual void AppendChild(XdmNode child) {

        }
        /// <summary>
        /// Inserts the specified node immediately after the specified reference node.
        /// </summary>
        /// <param name="newChild">The new child.</param>
        /// <param name="refChild">The ref child.</param>
        /// <returns></returns>
        public virtual XdmNode InsertAfter(XdmNode newChild, XdmNode refChild) {
            return newChild;
        }

        /// <summary>
        /// Inserts the specified node immediately before the specified reference node.
        /// </summary>
        /// <param name="newChild">The new child.</param>
        /// <param name="refChild">The ref child.</param>
        /// <returns></returns>
        public virtual XdmNode InsertBefore(XdmNode newChild, XdmNode refChild) {
            return newChild;
        }

        /// <summary>
        /// Replaces the child node oldChild with newChild node.
        /// </summary>
        /// <param name="newChild">The new child.</param>
        /// <param name="oldChild">The old child.</param>
        /// <returns></returns>
        public virtual XdmNode ReplaceChild(XdmNode newChild, XdmNode oldChild) {
            return newChild;
        }

        /// <summary>
        /// Adds the specified node to the beginning of the list of child nodes for this node.
        /// </summary>
        /// <param name="newChild">The new child.</param>
        /// <returns></returns>
        public virtual XdmNode PrependChild(XdmNode newChild) {
            return newChild;
        }



        public new XdmNode LastChild {
            get {
                return GetLastChild() as XdmNode;
            }
        }

        public new XdmDocument OwnerDocument {
            get {
                return GetOwnerDocument() as XdmDocument;
            }
        }

        internal override Node GetOwnerDocument() {
            if (_parent != null) {
                if (this._parent.NodeType == XdmNodeType.Document) {
                    return (XdmDocument)this._parent;
                }
                return this._parent.GetOwnerDocument() as XdmDocument;
            } else {
                XdmDocument doc = this as XdmDocument;
                if (doc != null)
                    return doc;
                return null;
            }
        }


        public virtual void RemoveChild(XdmNode childNode) {

        }

        internal virtual XdmNode FindChild(XdmNodeType type) {
            for (XdmNode node = (XdmNode)this.FirstChild; node != null; node = (XdmNode)node.NextNode) {
                if (node.NodeType == type) {
                    return node;
                }
            }
            return null;
        }
        public IEnumerator GetEnumerator() {
            return new XdmNodeList(this);
        }
        /// <summary>
        /// Gets the children.
        /// </summary>
        /// <value>The children.</value>
        public virtual XdmNodeList Children {
            get {
                return new XdmNodeList(this);
            }
        }

        public override string OuterXml {
            get {
                using (MemoryStream stream = new MemoryStream()) {
                    using (XmlWriter write = new XmlTextWriter(stream, null)) {
                        this.WriteTo(write);
                        write.Flush();
                        using (StreamReader reader = new StreamReader(stream)) {
                            return reader.ReadToEnd();
                        }
                    }
                }
            }
        }
        public override string InnerText {
            get {
                return this.StringValue;
            }
            set {
                switch (this.NodeType) {
                    case XdmNodeType.Text:
                    case XdmNodeType.Comment:
                        (this as XdmText)._strValue = value;
                        break;
                    case XdmNodeType.Attribute:
                    case XdmNodeType.Namespace:
                        (this as XdmAttribute)._strValue = value;
                        break;
                    case XdmNodeType.Element:
                        XdmElement elem = this as XdmElement;
                        while (elem.LastChild != null) elem.RemoveChild(elem.LastChild);
                        elem.AppendChild(elem.OwnerDocument.CreateTextNode(value));
                        break;
                }
            }
        }
        public override string InnerXml {
            get {
                using (MemoryStream stream = new MemoryStream()) {
                    using (XmlWriter write = new XmlTextWriter(stream, null)) {
                        this.WriteContentTo(write);
                        write.Flush();
                        stream.Position = 0;
                        using (StreamReader reader = new StreamReader(stream)) {
                            return reader.ReadToEnd();
                        }
                    }
                }
            }
            set {
                //TODO
            }
        }
        #endregion
    }

}
